package com.hhd.flow.engine.cmd.impl.virtual;

import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.hhd.flow.engine.cmd.PassTaskContext;
import com.hhd.flow.engine.cmd.impl.AbstractCommand;
import com.hhd.flow.engine.utils.CommonUtils;
import com.hhd.flow.engine.utils.FlowUtils;
import com.hhd.flow.engine.vo.*;
import com.hhd.flow.entity.FlowGatewayExecution;
import com.hhd.flow.entity.FlowTask;
import com.hhd.flow.entity.FlowUserTask;
import com.hhd.flow.exception.FlowException;
import com.tang.script.command.BooleanCommand;
import com.tang.script.variable.VariablesContainer;
import lombok.Getter;
import lombok.Setter;
import lombok.experimental.SuperBuilder;

import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@Getter
@Setter
@SuperBuilder
public abstract class VirtualPassAbleCommand<T> extends AbstractCommand<T> {
    protected void passTask(VirtualContext virtualContext) {
        PassTaskContext context = createPassTaskContext(virtualContext);
        autoComplete(virtualContext);

        while (true) {
            List<FlowTask> list = new LinkedList<>(virtualContext.getActiveTasks());

            for (FlowTask flowTask : list) {
                if (canPass(flowTask, context)) {
                    pass(flowTask, context);
                }
            }
            if (isAllSample(list, context.getVirtualContext().getActiveTasks())) break;
            autoComplete(virtualContext);
        }
    }

    private boolean isAllSample(List<FlowTask> before, List<FlowTask> after) {
        if (before.size() != after.size()) {
            return false;
        }
        for (FlowTask flowTask : before) {
            if (!after.stream().anyMatch(t -> t.getId().equals(flowTask.getId()))) {
                return false;
            }
        }
        return true;
    }

    private void autoComplete(VirtualContext context) {
        Set<String> historyUserIds = context.getHistoryUsers().stream().map(FlowUser::getId).collect(Collectors.toSet());
        List<FlowUserTask> tempFlowUserTasks = new LinkedList<>(context.getActiveUserTasks());
        List<FlowTask> tempFlowTasks = new LinkedList<>(context.getActiveTasks());

        List<FlowUserTask> userTasks = context.getActiveUserTasks().stream().filter(flowUserTask -> {
            if(!flowUserTask.getSupportAutoComplete()) return true;
            if(!historyUserIds.contains(flowUserTask.getUserId())) return true;

            FlowTask flowTask = this.getFlowTask(flowUserTask, tempFlowTasks);
            if(CommonUtils.isEmpty(flowTask)) return true;

            if(canAutoPass(flowTask, flowUserTask, tempFlowUserTasks)) {
                if (historyUserIds.contains(flowUserTask.getUserId())) {
                    flowTask.setExecuteCount(flowTask.getExecuteCount() + 1);
                }
            }

            return true;
        }).collect(Collectors.toList());
        context.setActiveUserTasks(userTasks);
//        List<FlowUser> historyUsers = context.getHistoryUsers();
//        Set<String> historyUserIds = historyUsers.stream().map(FlowUser::getId).collect(Collectors.toSet());
//        List<FlowUserTask> userTasks = context.getActiveUserTasks().stream().filter(flowUserTask -> {
//            if (historyUserIds.contains(flowUserTask.getUserId())) {
//                List<FlowTask> activeTasks = context.getActiveTasks();
//                for (FlowTask activeTask : activeTasks) {
//                    if (flowUserTask.getTaskInstanceId().equals(activeTask.getId())) {
//                        activeTask.setExecuteCount(activeTask.getExecuteCount() + 1);
//                    }
//                }
//                return false;
//            }
//            return true;
//        }).collect(Collectors.toList());
//        context.setActiveUserTasks(userTasks);
    }

    private FlowTask getFlowTask(FlowUserTask flowUserTask, List<FlowTask> tempFlowTasks) {
        for (FlowTask tempFlowTask : tempFlowTasks) {
            if (tempFlowTask.getId().equals(flowUserTask.getTaskInstanceId())) {
                return tempFlowTask;
            }
        }
        return null;
    }

    private boolean canAutoPass(FlowTask flowTask, FlowUserTask flowUserTask, List<FlowUserTask> tempFlowUserTasks) {
        List<FlowUserTask> flowUserTasks = tempFlowUserTasks.stream().filter(t -> t.getTaskInstanceId().equals(flowTask.getId())).collect(Collectors.toList());
        if(!flowUserTask.getSupportAutoComplete()) return false;
        UserTask.ExecuteWay executeWay = UserTask.ExecuteWay.valueOf(flowTask.getExecuteWay());
        if (executeWay == UserTask.ExecuteWay.single) {
            if (flowUserTasks.size() <= 1) return true;
            return false;
        }

        if (executeWay == UserTask.ExecuteWay.multiCompetition) {
            if (flowUserTasks.size() <= 1) return true;
            return false;
        }

        if (executeWay == UserTask.ExecuteWay.multiSequence) {
            return true;
        }

        if (executeWay == UserTask.ExecuteWay.multiSequence) {
            return true;
        }
        return false;
    }

    private List<FlowTask> pass(FlowTask flowTask, PassTaskContext context) {
        FlowNode flowNode = context.getFlowModel().getNodeById(flowTask.getTaskDefinitionId());
        if (flowNode.getType() == FlowNode.FlowNodeType.start) passStart(flowTask, context);
        if (flowNode.getType() == FlowNode.FlowNodeType.userTask) passUserTask(flowNode, flowTask, context);
        if (flowNode.getType() == FlowNode.FlowNodeType.end) passEndTask(flowNode, flowTask, context);
        if (flowNode.getType() == FlowNode.FlowNodeType.empty) passEmptyTask(flowNode, flowTask, context);

        //向下流转
        if (flowNode.getType() == FlowNode.FlowNodeType.gateway) {
            Gateway gateway = context.getFlowModel().getGatewaById(flowNode.getId());
            if (gateway.isStart()) {
                passStartGateway(gateway, flowTask, context);
                return branchForStartGateway(gateway, flowTask, context);
            } else {
                passEndGateway(gateway, flowTask, context);
            }
        }
        return passNextNodes(flowNode, context);
    }

    private void passEndGateway(Gateway gateway, FlowTask flowTask, PassTaskContext context) {
        context.getVirtualContext().getActiveTasks().remove(flowTask);

        List<FlowGatewayExecution> executions = context.getVirtualContext()
                .getGatewayExecutions().stream()
                .filter(e -> e.getEndGatewayDefinitionId().equals(gateway.getId()))
                .collect(Collectors.toList());

        if (!executions.isEmpty()) {
            context.getVirtualContext().getGatewayExecutions().remove(executions.get(0));
        }
    }

    private void passEmptyTask(FlowNode flowNode, FlowTask flowTask, PassTaskContext context) {
        context.getVirtualContext().getActiveTasks().remove(flowTask);
    }

    private void passEndTask(FlowNode flowNode, FlowTask flowTask, PassTaskContext context) {
        context.getVirtualContext().getActiveTasks().remove(flowTask);
    }

    private void passUserTask(FlowNode flowNode, FlowTask flowTask, PassTaskContext context) {
        context.getVirtualContext().getActiveTasks().remove(flowTask);
    }

    private List<FlowTask> branchForStartGateway(Gateway gateway, FlowTask flowTask, PassTaskContext context) {
        List<FlowTask> res = CommonUtils.newList();
        List<FlowNodeAndEdge> nodeAndEdges = context.getFlowModel().getNextNodeAndEdgesById(gateway.getId());
        nodeAndEdges = filterEdgeForGateway(nodeAndEdges, gateway, context);

        FlowGatewayExecution gateWayExecution = FlowUtils.createGateWayExecution(flowTask, gateway, context, nodeAndEdges.size());
        gateWayExecution.setId(IdWorker.getIdStr());
        context.getVirtualContext().getGatewayExecutions().add(gateWayExecution);

        for (FlowNodeAndEdge nodeAndEdge : nodeAndEdges) {
            res.addAll(enter(nodeAndEdge, context));
        }
        context.getVirtualContext().getActiveTasks().addAll(res);
        return res;
    }

    private List<FlowNodeAndEdge> filterEdgeForGateway(List<FlowNodeAndEdge> nodeAndEdges,
                                                       Gateway gateway,
                                                       PassTaskContext context) {
        VariablesContainer variablesContainer = getVariablesContainer(context.getFormTypeId(), context.getFormId(), context.getFlowProcessInstance(), context.getFormModel());
        //单分支网关
        if (gateway.getExecuteWay() == Gateway.ExecuteWay.exclusive) {
            for (FlowNodeAndEdge nodeAndEdge : nodeAndEdges) {
                String formula = nodeAndEdge.getEdge().getFormula();
                if (CommonUtils.isEmpty(formula)) return CommonUtils.asList(nodeAndEdge);

                BooleanCommand booleanCommand = getEngine().getScriptCommandBuilder().newBooleanCommand()
                        .scriptText(formula).variablesContainer(variablesContainer).build();
                if (getEngine().executeScript(booleanCommand).getResult()) {
                    return CommonUtils.asList(nodeAndEdge);
                }
            }
            throw new FlowException("所有的分支条件均不满足, 无法向下继续执行");
        }

        //多分支网关
        List<FlowNodeAndEdge> res = CommonUtils.newList();
        for (FlowNodeAndEdge nodeAndEdge : nodeAndEdges) {
            String formula = nodeAndEdge.getEdge().getFormula();
            if (CommonUtils.isEmpty(formula)) {
                res.add(nodeAndEdge);
                continue;
            }

            BooleanCommand booleanCommand = getEngine().getScriptCommandBuilder().newBooleanCommand()
                    .scriptText(formula).variablesContainer(variablesContainer).build();
            if (getEngine().executeScript(booleanCommand).getResult()) {
                res.add(nodeAndEdge);
            }
        }
        CommonUtils.isEmptyThrow(res, new FlowException("所有的分支条件均不满足, 无法向下继续执行"));
        return res;
    }

    protected void passStartGateway(Gateway gateway, FlowTask flowTask, PassTaskContext context) {
        context.getVirtualContext().getActiveTasks().remove(flowTask);
    }

    private List<FlowTask> passNextNodes(FlowNode flowNode, PassTaskContext context) {
        List<FlowTask> res = CommonUtils.newList();
        List<FlowNodeAndEdge> nodeAndEdges = context.getFlowModel().getNextNodeAndEdgesById(flowNode.getId());
        for (FlowNodeAndEdge nodeAndEdge : nodeAndEdges) {
            res.addAll(enter(nodeAndEdge, context));
        }
        context.getVirtualContext().getActiveTasks().addAll(res);
        return res;
    }

    private List<FlowTask> enter(FlowNodeAndEdge nodeAndEdge, PassTaskContext context) {
        FlowNode flowNode = nodeAndEdge.getNode();
        List<FlowTask> res = CommonUtils.newList();
        if (flowNode.getType() == FlowNode.FlowNodeType.userTask) res = enterUserTask(flowNode, context);
        if (flowNode.getType() == FlowNode.FlowNodeType.empty) res = enterEmpty(flowNode, context);
        if (flowNode.getType() == FlowNode.FlowNodeType.end) res = CommonUtils.newList();
        if (flowNode.getType() == FlowNode.FlowNodeType.gateway) {
            Gateway gateway = context.getFlowModel().getGatewaById(flowNode.getId());
            if (gateway.isStart()) res = enterStartGateway(gateway, context);
            if (gateway.isEnd()) res = enterEndGateway(gateway, context);
        }
        return res;
    }

    protected List<FlowTask> enterEndGateway(Gateway endGateway, PassTaskContext context) {
        List<FlowTask> flowTasks = context.getVirtualContext().getActiveTasks()
                .stream().filter(task -> task.getTaskDefinitionId().equals(endGateway.getId()))
                .collect(Collectors.toList());

        if (flowTasks.isEmpty()) {
            FlowTask endGatewayTask = FlowUtils.createEndGatewayTask(endGateway, context.getFlowProcessInstance());
            endGatewayTask.setExecuteCount(1);
            endGatewayTask.setId(IdWorker.getIdStr());

            List<FlowGatewayExecution> executions = context.getVirtualContext().getGatewayExecutions()
                    .stream().filter(e -> e.getEndGatewayDefinitionId().equals(endGateway.getId()))
                    .collect(Collectors.toList());
            if (!executions.isEmpty()) {
                FlowGatewayExecution flowGatewayExecution = executions.get(0);
                endGatewayTask.setExecuteThreshold(flowGatewayExecution.getExecuteThreshold());
            } else {
                //流程到了网关结束的节点, 但是没有找到网关开始节点创建出来的execution,
                // 这个问题会出现在指定回退节点的情况下, 如果是这种情况, 那就直接忽略网关结束节点, 让流程继续向后执行
            }
            return CommonUtils.asList(endGatewayTask);
        } else {
            FlowTask flowTask = flowTasks.get(0);
            flowTask.setExecuteCount(flowTask.getExecuteCount() + 1);
            return CommonUtils.newList();
        }
    }

    protected List<FlowTask> enterStartGateway(Gateway gateway, PassTaskContext context) {
        //进入网关的时候, 只是创建网关任务, 并且添加路径
        FlowTask gatewayTask = FlowUtils.createStartGatewayTask(gateway, context.getFlowProcessInstance());
        gatewayTask.setId(IdWorker.getIdStr());
        return CommonUtils.asList(gatewayTask);
    }

    private List<FlowTask> enterEmpty(FlowNode flowNode, PassTaskContext context) {
        FlowTask emptyTask = FlowUtils.createEmptyTask(flowNode, context.getFlowProcessInstance());
        emptyTask.setId(IdWorker.getIdStr());
        return CommonUtils.asList(emptyTask);
    }

    private List<FlowTask> enterUserTask(FlowNode flowNode, PassTaskContext context) {
        UserTask userTask = context.getFlowModel().getUserTaskById(flowNode.getId());
        List<FlowUser> flowUsers = loadFlowUsers(context, userTask);
        FlowTask flowTask = createFlowTaskForUserTask(context, userTask, flowUsers);
        List<FlowUserTask> flowUserTasks = createFlowUserTasks(flowTask, flowUsers, context, userTask);
        context.getVirtualContext().getActiveUserTasks().addAll(flowUserTasks);

        addMenuSelect(flowUsers, userTask, context);
        return CommonUtils.asList(flowTask);
    }

    private void addMenuSelect(List<FlowUser> flowUsers, UserTask userTask, PassTaskContext context) {
        if (flowUsers.size() > 1 && userTask.isManualSelectUser()) {
            context.getUserManualSelect().addSelectOption(FlowUtils.createSelectOption(flowUsers, userTask, context));
        }
    }

    private List<FlowUser> loadFlowUsers(PassTaskContext context, UserTask userTask) {
        if (userTask.isAddCountSign()) {
            return FlowUtils.getAddCountSignUsers(userTask);
        }
        List<FlowUser> flowUsers = getEngine().loadFlowUser(context.getFormTypeId(), context.getFormId(), userTask, context);
        if (CommonUtils.isEmpty(flowUsers)) {
            //任务配置没有找到用户，这里可以把所有的用户都找出来
//            userTask.setManualSelectUser(true);
//            return selectAllUsers;
        }
        return flowUsers;
    }

    private FlowTask createFlowTaskForUserTask(PassTaskContext context, UserTask userTask, List<FlowUser> flowUsers) {
        FlowTask flowTask = FlowUtils.createFlowTask(userTask, flowUsers, context);
        flowTask.setId(IdWorker.getIdStr());
        return flowTask;
    }

    private List<FlowUserTask> createFlowUserTasks(FlowTask flowTask, List<FlowUser> flowUsers, PassTaskContext context, UserTask userTask) {
        UserTask.ExecuteWay executeWay = UserTask.ExecuteWay.forName(flowTask.getExecuteWay());
        List<FlowUserTask> flowUserTasks = CommonUtils.newList();
        if (executeWay == UserTask.ExecuteWay.single
                || executeWay == UserTask.ExecuteWay.multi
                || userTask.isNotify() //如果是知会, 那就直接把所有的用户任务都创建出来
                || executeWay == UserTask.ExecuteWay.multiCompetition) {
            //如果单用户找到了多个人，那就直接当做多用户审批
            flowUserTasks = FlowUtils.createFlowUserTasks(flowTask, flowUsers, context.getFlowProcessInstance(), userTask);
        } else if (executeWay == UserTask.ExecuteWay.multiSequence) {
            flowUserTasks = FlowUtils.createFlowUserTasks(flowTask, CommonUtils.asList(flowUsers.get(0)), context.getFlowProcessInstance(), userTask);
        }
        for (FlowUserTask flowUserTask : flowUserTasks) {
            flowUserTask.setId(IdWorker.getIdStr());
        }
        if (userTask.isManualSelectUser() && flowUserTasks.size() > 1) {//如果当前节点需要手动选择，那么就不再向下流转
            for (FlowUserTask flowUserTask : flowUserTasks) {
                flowUserTask.setSupportAutoComplete(false);
            }
        }
        return flowUserTasks;
    }

    private PassTaskContext createPassTaskContext(VirtualContext context) {
        return PassTaskContext.builder()
                .variablesContainer(getVariablesContainer(context.formTypeId, context.getFormId(), context.processInstance, context.formModel))
                .formTypeId(context.formTypeId)
                .formId(context.formId)
                .flowModel(context.getFlowModel())
                .flowProcessInstanceId(context.getProcessInstance().getId())
                .userManualSelect(context.userManualSelect)
                .formModel(context.formModel)
                .flowProcessInstance(context.processInstance)
                .virtualContext(context)
                .build();
    }


    private boolean canPass(FlowTask flowTask, PassTaskContext context) {
        FlowNode flowNode = context.getFlowModel().getNodeById(flowTask.getTaskDefinitionId());
        if (flowNode.getType() == FlowNode.FlowNodeType.start) return true;
        if (flowNode.getType() == FlowNode.FlowNodeType.empty) return true;
        if (flowNode.getType() == FlowNode.FlowNodeType.end) return true;
        if (flowNode.getType() == FlowNode.FlowNodeType.userTask) return canPassUserTask(flowTask, context);
        if (flowNode.getType() == FlowNode.FlowNodeType.gateway) {
            Gateway gateway = context.getFlowModel().getGatewaById(flowNode.getId());
            CommonUtils.isEmptyThrow(gateway, new FlowException("没有找到网关: " + flowNode.getId()));
            if (gateway.isStart()) return true;
            if (gateway.isEnd()) return canPassEndGateway(context, gateway, flowTask);
        }

        return false;
    }

    private boolean canPassEndGateway(PassTaskContext context, Gateway endGateway, FlowTask flowTask) {
        return flowTask.getExecuteCount() >= flowTask.getExecuteThreshold();
    }

    private boolean canPassUserTask(FlowTask flowTask, PassTaskContext context) {
        return flowTask.getExecuteCount() >= flowTask.getExecuteThreshold();
    }

    private void passStart(FlowTask flowTask, PassTaskContext context) {
        context.getVirtualContext().getActiveTasks().remove(flowTask);
    }
}
